import { MarketCache, PoolCache } from './cache';
import { Listeners } from './listeners';
import { Connection, KeyedAccountInfo, Keypair } from '@solana/web3.js';
import { LIQUIDITY_STATE_LAYOUT_V4, MARKET_STATE_LAYOUT_V3, Token, TokenAmount } from '@raydium-io/raydium-sdk';
import { AccountLayout, getAssociatedTokenAddressSync } from '@solana/spl-token';
import { Bot, BotConfig } from './bot';
import { DefaultTransactionExecutor, TransactionExecutor } from './transactions';
import {
  getToken,
  getWallet,
  logger,
  COMMITMENT_LEVEL,
  RPC_ENDPOINT0,
  RPC_ENDPOINT,
  RPC_ENDPOINT1,
  RPC_ENDPOINT2,
  RPC_WEBSOCKET_ENDPOINT0,
  RPC_WEBSOCKET_ENDPOINT,
  RPC_WEBSOCKET_ENDPOINT1,
  RPC_WEBSOCKET_ENDPOINT2,
  PRE_LOAD_EXISTING_MARKETS,
  LOG_LEVEL,
  CHECK_IF_MUTABLE,
  CHECK_IF_MINT_IS_RENOUNCED,
  CHECK_IF_FREEZABLE,
  CHECK_IF_BURNED,
  QUOTE_MINT,
  MAX_POOL_SIZE,
  MIN_POOL_SIZE,
  QUOTE_AMOUNT,
  PRIVATE_KEY,
  USE_SNIPE_LIST,
  ONE_TOKEN_AT_A_TIME,
  AUTO_SELL_DELAY,
  MAX_SELL_RETRIES,
  AUTO_SELL,
  MAX_BUY_RETRIES,
  AUTO_BUY_DELAY,
  COMPUTE_UNIT_LIMIT,
  COMPUTE_UNIT_PRICE,
  CACHE_NEW_MARKETS,
  TAKE_PROFIT,
  STOP_LOSS,
  BUY_SLIPPAGE,
  SELL_SLIPPAGE,
  PRICE_CHECK_DURATION,
  PRICE_CHECK_INTERVAL,
  SNIPE_LIST_REFRESH_INTERVAL,
  TRANSACTION_EXECUTOR,
  CUSTOM_FEE,
  FILTER_CHECK_INTERVAL,
  FILTER_CHECK_DURATION,
  CONSECUTIVE_FILTER_MATCHES,
  ENABLE_WALLET_PROTECTION,
  MIN_WALLET_BALANCE,
  MAX_WALLET_BALANCE,
} from './helpers';
import { version } from './package.json';
import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
import { JitoTransactionExecutor } from './transactions/jito-rpc-transaction-executor';

/**
 * @description 1刀的rpc 最主要Liquidity.fetchInfo，market监听
 */
const connection = new Connection(RPC_ENDPOINT, {
  wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
  commitment: COMMITMENT_LEVEL,
});

/**
 * @description github的rpc轮询卖出，开始前校验钱包代币，轮询中校验钱包代币，卖出前是校验钱包余额是否存在代币
 */
const connection0 = new Connection(RPC_ENDPOINT0, {
  wsEndpoint: RPC_WEBSOCKET_ENDPOINT0,
  commitment: COMMITMENT_LEVEL,
});

/**
 * @description # chenyuning的rpc 获取最新区块（买卖的核心）+卖出后检测钱包
 */
const connection1 = new Connection(RPC_ENDPOINT1, {
  wsEndpoint: RPC_WEBSOCKET_ENDPOINT1,
  commitment: COMMITMENT_LEVEL,
});

/**
 * @description # chenyuning的rpc，池子流动性检测+其他杂活（初始validate，浮动止损计算，30秒循环校验钱包余额）
 */
const connection2 = new Connection(RPC_ENDPOINT2, {
  wsEndpoint: RPC_WEBSOCKET_ENDPOINT2,
  commitment: COMMITMENT_LEVEL,
});

// console.log(connection1, 'RPC_ENDPOINT1', RPC_ENDPOINT1, 'RPC_WEBSOCKET_ENDPOINT1', RPC_WEBSOCKET_ENDPOINT1);

function printDetails(wallet: Keypair, quoteToken: Token, bot: Bot) {
  // logger.info(`

  //         ██     ██    █████    ██████    ██████  
  //         ██     ██   ██   ██   ██   ██   ██   ██ 
  //         ██  █  ██   ███████   ██████    ██████  
  //         ██ ███ ██   ██   ██   ██   ██   ██      
  //         ███ ███ ██  ██   ██   ██████    ██      
                                     
     
  //              🚀 WARP DRIVE ACTIVATED 🚀
  //                Made with ❤️ by humans 
  //                 Version: ${version}
  //   `);

  const botConfig = bot.config;

  logger.info('------- 配置信息开始 -------');
  logger.info(`钱包地址: ${wallet.publicKey.toString()}`);

  logger.info('- 机器人配置 -');
  logger.info(
    `使用 ${TRANSACTION_EXECUTOR} 执行器: ${bot.isWarp || bot.isJito || (TRANSACTION_EXECUTOR === 'default' ? true : false)}`,
  );
  logger.info(`${TRANSACTION_EXECUTOR} 费用: ${CUSTOM_FEE}`);
  logger.info(`计算单元限制: ${botConfig.unitLimit}`);
  logger.info(`计算单元价格 (微 lamports): ${botConfig.unitPrice}`);

  logger.info(`单次交易一个代币: ${botConfig.oneTokenAtATime}`);
  logger.info(`预加载现有市场: ${PRE_LOAD_EXISTING_MARKETS}`);
  logger.info(`缓存新市场: ${CACHE_NEW_MARKETS}`);
  logger.info(`日志级别: ${LOG_LEVEL}`);

  logger.info('- 买入配置 -');
  logger.info(`买入数量: ${botConfig.quoteAmount.toFixed()} ${botConfig.quoteToken.name}`);
  logger.info(`自动买入延迟: ${botConfig.autoBuyDelay} 毫秒`);
  logger.info(`最大买入重试次数: ${botConfig.maxBuyRetries}`);
  logger.info(`买入数量 (${quoteToken.symbol}): ${botConfig.quoteAmount.toFixed()}`);
  logger.info(`买入滑点: ${botConfig.buySlippage}%`);

  logger.info('- 卖出配置 -');
  logger.info(`自动卖出: ${AUTO_SELL}`);
  logger.info(`自动卖出延迟: ${botConfig.autoSellDelay} 毫秒`);
  logger.info(`最大卖出重试次数: ${botConfig.maxSellRetries}`);
  logger.info(`卖出滑点: ${botConfig.sellSlippage}%`);
  logger.info(`价格检查间隔: ${botConfig.priceCheckInterval} 毫秒`);
  logger.info(`价格检查持续时间: ${botConfig.priceCheckDuration} 毫秒`);
  logger.info(`止盈比例: ${botConfig.takeProfit}%`);
  logger.info(`止损比例: ${botConfig.stopLoss}%`);

  logger.info('- 狙击列表配置 -');
  logger.info(`使用狙击列表: ${botConfig.useSnipeList}`);
  logger.info(`狙击列表刷新间隔: ${SNIPE_LIST_REFRESH_INTERVAL} 毫秒`);

  if (botConfig.useSnipeList) {
    logger.info('- 过滤器配置 -');
    logger.info(`启用狙击列表时过滤器已禁用`);
  } else {
    logger.info('- 过滤器配置 -');
    logger.info(`过滤器检查间隔: ${botConfig.filterCheckInterval} 毫秒`);
    logger.info(`过滤器检查持续时间: ${botConfig.filterCheckDuration} 毫秒`);
    logger.info(`连续匹配次数: ${botConfig.consecutiveMatchCount}`);
    logger.info(`检查是否放弃铸币权: ${botConfig.checkRenounced}`);
    logger.info(`检查是否可冻结: ${botConfig.checkFreezable}`);
    logger.info(`检查是否已销毁: ${botConfig.checkBurned}`);
    logger.info(`最小池大小: ${botConfig.minPoolSize.toFixed()}`);
    logger.info(`最大池大小: ${botConfig.maxPoolSize.toFixed()}`);
  }

  logger.info('------- 配置信息结束 -------');
  logger.info('机器人正在运行中！按 CTRL + C 停止运行。');
}

const runListener = async () => {
  logger.level = LOG_LEVEL;
  logger.info('Bot is starting...');

  const marketCache = new MarketCache(connection1);
  const poolCache = new PoolCache();
  let txExecutor: TransactionExecutor;

  switch (TRANSACTION_EXECUTOR) {
    case 'warp': {
      txExecutor = new WarpTransactionExecutor(CUSTOM_FEE);
      break;
    }
    case 'jito': {
      txExecutor = new JitoTransactionExecutor(CUSTOM_FEE, connection);
      break;
    }
    default: {
      txExecutor = new DefaultTransactionExecutor(connection);
      break;
    }
  }

  const wallet = getWallet(PRIVATE_KEY.trim());

  logger.info(`当前钱包：${wallet}`);
  const quoteToken = getToken(QUOTE_MINT);
  const botConfig = <BotConfig>{
    wallet,
    quoteAta: getAssociatedTokenAddressSync(quoteToken.mint, wallet.publicKey),
    checkRenounced: CHECK_IF_MINT_IS_RENOUNCED,
    checkFreezable: CHECK_IF_FREEZABLE,
    checkBurned: CHECK_IF_BURNED,
    minPoolSize: new TokenAmount(quoteToken, MIN_POOL_SIZE, false),
    maxPoolSize: new TokenAmount(quoteToken, MAX_POOL_SIZE, false),
    quoteToken,
    quoteAmount: new TokenAmount(quoteToken, QUOTE_AMOUNT, false),
    oneTokenAtATime: ONE_TOKEN_AT_A_TIME,
    useSnipeList: USE_SNIPE_LIST,
    autoSell: AUTO_SELL,
    autoSellDelay: AUTO_SELL_DELAY,
    maxSellRetries: MAX_SELL_RETRIES,
    autoBuyDelay: AUTO_BUY_DELAY,
    maxBuyRetries: MAX_BUY_RETRIES,
    unitLimit: COMPUTE_UNIT_LIMIT,
    unitPrice: COMPUTE_UNIT_PRICE,
    takeProfit: TAKE_PROFIT,
    stopLoss: STOP_LOSS,
    buySlippage: BUY_SLIPPAGE,
    sellSlippage: SELL_SLIPPAGE,
    priceCheckInterval: PRICE_CHECK_INTERVAL,
    priceCheckDuration: PRICE_CHECK_DURATION,
    filterCheckInterval: FILTER_CHECK_INTERVAL,
    filterCheckDuration: FILTER_CHECK_DURATION,
    consecutiveMatchCount: CONSECUTIVE_FILTER_MATCHES,
  };

  const bot = new Bot(connection, connection0, connection1, connection2, marketCache, poolCache, txExecutor, botConfig);
  try {
    const valid = await bot.validate();

    if (!valid) {
      logger.info('Bot is exiting...');
      process.exit(1);
    }
  } catch (err) {
    logger.error(err);
  }

  if (PRE_LOAD_EXISTING_MARKETS) {
    await marketCache.init({ quoteToken });
  }

  const runTimestamp = Math.floor(new Date().getTime() / 1000);
  const listeners = new Listeners(connection0);
  await listeners.start({
    walletPublicKey: wallet.publicKey,
    quoteToken,
    autoSell: AUTO_SELL,
    cacheNewMarkets: CACHE_NEW_MARKETS,
  });

  listeners.on('market', (updatedAccountInfo: KeyedAccountInfo) => {
    const marketState = MARKET_STATE_LAYOUT_V3.decode(updatedAccountInfo.accountInfo.data);
    marketCache.save(updatedAccountInfo.accountId.toString(), marketState);
  });

  listeners.on('pool', async (updatedAccountInfo: KeyedAccountInfo) => {
    const poolState = LIQUIDITY_STATE_LAYOUT_V4.decode(updatedAccountInfo.accountInfo.data);
    const poolOpenTime = parseInt(poolState.poolOpenTime.toString());
    const exists = await poolCache.get(poolState.baseMint.toString());
    // logger.info(`池子信息`, poolOpenTime);

    if (!exists && poolOpenTime > runTimestamp) {
      logger.info(`新池子`, poolState);

      poolCache.save(updatedAccountInfo.accountId.toString(), poolState);
      await bot.buy(updatedAccountInfo.accountId, poolState);
    }
  });

  listeners.on('wallet', async (updatedAccountInfo: KeyedAccountInfo) => {
    const accountData = AccountLayout.decode(updatedAccountInfo.accountInfo.data);

    if (accountData.mint.equals(quoteToken.mint)) {
      return;
    }

    await bot.sell(updatedAccountInfo.accountId, accountData);
  });

  printDetails(wallet, quoteToken, bot);
};

runListener();
